package net.funafuna.funamail.smtp;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import net.funafuna.funamail.Message;
import net.funafuna.funamail.Util;

public class SmtpClient {

    private static final String CRLF = "\r\n";
    private static final String MUA_VERSION = "funamail 0.0.1";

    /** SMTP information */
    private Smtp smtp;

    /** SMTP auth information */
    private SmtpAuth smtpAuth;

    private Socket socket;
    private BufferedReader br;
    private BufferedWriter bw;

    public SmtpClient(Smtp smtp) {
        this.smtp = smtp;
    }

    public synchronized void setSmtp(Smtp smtp) {
        this.smtp = smtp;
    }

    public synchronized void setSmtpAuth(SmtpAuth smtpAuth) {
        this.smtpAuth = smtpAuth;
    }

    private void close() {
        try {
            this.socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.socket = null;
        this.br = null;
        this.bw = null;
    }

    private void connect() throws IOException {
        if (this.smtp.isUseSSL()) {
            final SSLContext context;
            try {
                context = SSLContext.getInstance("SSL");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            try {
                context.init(null, null, null);
            } catch (KeyManagementException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            final SSLSocketFactory sf = context.getSocketFactory();
            try {
                this.socket = sf.createSocket(this.smtp.getHost(), this.smtp.getPort());
            } catch (UnknownHostException e) {
                e.printStackTrace();
                throw e;
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }
        } else {
            try {
                this.socket = new Socket(this.smtp.getHost(), this.smtp.getPort());
            } catch (UnknownHostException e) {
                e.printStackTrace();
                throw e;
            } catch (IOException e) {
                e.printStackTrace();
                throw e;
            }
        }
        this.socket.setSoTimeout(10000);

        try {
            final InputStream is = socket.getInputStream();
            this.br = new BufferedReader(new InputStreamReader(is));

            final OutputStream os = socket.getOutputStream();
            this.bw = new BufferedWriter(new OutputStreamWriter(os, "x-windows-iso2022jp"));
        } catch (IOException e) {
            close();
            throw e;
        }
    }

    private SmtpResponse getResponse() throws SmtpException, IOException {
        while (true) {
            final String message = this.br.readLine();
            if (message == null) {
                throw new SmtpException("message is null.");
            }
            if (message.length() < 4) {
                throw new SmtpException("message is invalid. message=[" + message + "]");
            }
            if (message.matches("^\\d{3} .*")) {
                final int code = Integer.parseInt(message.substring(0, 3));
                final String mes = message.substring(4);
                return new SmtpResponse(code, mes);
            } else {
                System.out.println(message);
                continue;
            }
        }
    }

    public synchronized void send(final Message message) throws Exception {

        {
            connect();
            final SmtpResponse res = getResponse();
            System.out.println(res);
        }
        {
            write("EHLO localhost");
            write(CRLF);
            flush();
            final SmtpResponse res = getResponse();
            System.out.println(res);
        }
        {
            if (this.smtpAuth != null) {
                write("AUTH PLAIN ");
                write(Util.createLoginInfo(this.smtpAuth.getUserName(), this.smtpAuth.getPassword()));
                write(CRLF);
                flush();
                final SmtpResponse res = getResponse();
                System.out.println(res);
            }
        }
        {
            write("MAIL FROM:");
            write(message.getMailFrom());
            write(CRLF);
            flush();
            final SmtpResponse res = getResponse();
            System.out.println(res);
        }
        {
            for (String to : message.getRcptTo()) {
                write("RCPT TO:");
                write(to);
                write(CRLF);
                flush();
                final SmtpResponse res = getResponse();
                System.out.println(res);
            }
        }
        {
            write("DATA");
            write(CRLF);
            flush();
            final SmtpResponse res = getResponse();
            System.out.println(res);
        }
        {

            final Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
            write("Date: " + sdf.format(date));
            write(CRLF);

            write(message.getFrom());
            write(CRLF);

            write(message.getSubject());
            write(CRLF);

            write(message.getTo());
            write(CRLF);

            final String cc = message.getCc();
            if (cc != null && !cc.equals("")) {
                write(cc);
                write(CRLF);
            }

            final String references = message.getReferences();
            if (references != null && !references.equals("")) {
                write(references);
                write(CRLF);
            }

            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
            write("Message-Id: <" + sdf2.format(date) + "." + message.getMailFrom() + ">");
            write(CRLF);

            write("Mime-Version: 1.0");
            write(CRLF);

            write("Content-Type: text/plain; charset=ISO-2022-JP");
            write(CRLF);

            write("Content-Transfer-Encoding: 7bit");
            write(CRLF);

            final List<String> optionHeaders = message.getOptionHeaders();
            for (final String optHeader : optionHeaders) {
                write(optHeader);
                write(CRLF);
            }

            write("X-Mailer: " + MUA_VERSION);
            write(CRLF);

            write(CRLF);

            // TODO 改行コードを変換しないと。。。Linux、Macはやばい
            // TODO 「.」だけの行があったらどうする？メッセージの終了と認識してしまう。。。
            write(message.getContent());
            write(CRLF);

            write(CRLF);

            write(".");
            write(CRLF);
            flush();
            final SmtpResponse res = getResponse();
            System.out.println(res);
        }
        {
            write("QUIT");
            write(CRLF);
            flush();
            final SmtpResponse res = getResponse();
            System.out.println(res);
        }
        {
            close();
        }
    }

    private void write(String message) throws IOException {
        this.bw.write(message);
    }

    private void flush() throws IOException {
        this.bw.flush();
    }
}
